<?php

	namespace org\tekuna\plugin\simpletemplate;

	use \Exception;
	
	use org\tekuna\base\Tekuna;
	
	use org\tekuna\core\configuration\ConfigurationException;
	use org\tekuna\core\configuration\ConfigurationElement;

	use org\tekuna\framework\request\Request;
	use org\tekuna\framework\action\ActionEvent;
	use org\tekuna\framework\response\http\ContentResponse;
	use org\tekuna\framework\response\http\FiveOhOhResponse;
	use org\tekuna\framework\interceptor\AbstractInterceptor;
	use org\tekuna\framework\interceptor\InvocationChain;

	
	/**
	 * This Interceptor wraps (decorates) generated ContentResponses with
	 * templates that are defined at component level and at application level.
	 * 
	 * <component
	 *    ...
	 *    simple-template:decorator="component-decorator.php"
	 *    simple-template:plugin="myPlugin">
	 *    
	 * <tekuna:application
	 *    ...
	 *    simple-template:decorator="main-decorator.php"
	 *    simple-template:plugin="myPlugin">
	 *    
	 * The way of defining application level and component level decorators is 
	 * exaclty the same. The decorator attribute points to a PHP file, that
	 * is used as the decorating template. Make sure that this file contains 
	 * the following command:
	 * 
	 * <?php echo $decoratedContent; ?>
	 * 
	 * This is where the decorated content is inserted. The order of inclusion 
	 * is as follows:
	 * 
	 *   - execution of the action that returns a ContentResponse
	 *   - decoration with component decorator (if given)
	 *   - decoration with application decorator (if given)
	 * 
	 * If the plugin parameter is used the decoration template is loaded from
	 * out of the defined plugin.
	 */
	class DecorationInterceptor extends AbstractInterceptor {
		
		private $objRequest;
		
		
		public function setRequest(Request $objRequest) {
			
			$this -> objRequest = $objRequest;
		}
		
		
		/**
		 * This method intercepts the action call. If the returned response
		 * is of type ContentResponse, the templates defined at component level
		 * and application level are wrapped around the content in the ContentResponse.
		 * 
		 * @param InvocationChain $objChain the invocation chain
		 * @return Response
		 */
		public function intercept(InvocationChain $objChain) {
			
			// run the action (and other interceptors)
			try {
			
				$objResponse = $objChain -> invokeNext();
			}
			catch (Exception $objException) {

				Tekuna :: logException($objException);
				$objResponse = new FiveOhOhResponse($this -> getApplicationContext() -> getRequest(), $objException);
			}
			
			// decorate only HTTP Content Responses
			if ($objResponse instanceof ContentResponse) {
				
				// decorate with component
				$objComponentElement = $objChain -> getActionEvent() -> getComponentElement();
				$this -> applyDecorator($objComponentElement, $objResponse, $objChain -> getActionEvent());

				// decorate with application
				$objApplicationElement = $this -> getApplicationContext() -> getConfiguration() -> getRootElement();
				$this -> applyDecorator($objApplicationElement, $objResponse, $objChain -> getActionEvent());
			}
			
			// return possibly decorated response
			return $objResponse;
		}

		
		private function applyDecorator(ConfigurationElement $objDecoratedElement, ContentResponse $objResponse, ActionEvent $objActionEvent) {
			
			// check if there is defined a decorator
			if ($objDecoratedElement -> hasAttribute('simple-template', 'decorator')) {
				
				// get decorator file
				$sDecoratorFile = $objDecoratedElement -> getAttribute('simple-template', 'decorator') -> getValue();
				
				// load script from plugin if plugin defined
				if ($objDecoratedElement -> hasAttribute('simple-template', 'plugin')) {
					
					// load the plugin
					$sPluginKey = $objDecoratedElement -> getAttribute('simple-template', 'plugin') -> getValue();
					$objPlugin = $this -> getApplicationContext() -> getPluginManager() -> getPlugin($sPluginKey);
					
					// use a plugin resource
					$sDecoratorFile = $objPlugin -> getResourceFilename($sDecoratorFile);
				}
				
				// decorator file must exist
				if (! is_file($sDecoratorFile) || ! is_readable($sDecoratorFile)) {
					
					throw new ConfigurationException("The referenced decorator file '$sDecoratorFile' does not exist or is not readable.");
				}
				
				// log some information
				Tekuna :: getLogger(__CLASS__) -> info("Decorating response with decorator template '$sDecoratorFile'.");
				
				// perform the decoration
				$sDecoratedContent = $this -> performDecoration($objResponse -> getContent(), $sDecoratorFile, $objActionEvent);
				$objResponse -> setContent($sDecoratedContent);
			}
		}
		
		
		private function performDecoration($sDecoratedContent, $sDecoratorFile, ActionEvent $objActionEvent) {
			
			// convenience variables
			$decoratedContent = $sDecoratedContent;
			$context = $this -> getApplicationContext();
			$basePath = $objActionEvent -> buildActionUrl('') -> renderRelative();
			$selfPath = $this -> objRequest -> buildCurrentRequestUrl() -> renderRelative();
			
			// start buffering
			ob_start();
			
			try {
				// run decorator script in this method's context
				require $sDecoratorFile;
				
				// get the decorated content
				$sAfterDecoration = ob_get_contents();
				ob_end_clean();
			}
			catch (Exception $objException) {
				
				// poor man's finally...
				ob_end_clean();
				throw $objException;
			}
			
			// convert the content to the internal encoding
			$sEncoding = mb_detect_encoding($sAfterDecoration);
			
			if ($sEncoding === false) {
			
				throw new SimpleTemplateException("The encoding of the rendered decorator '$sDecoratorFile' could not be detected.");
			}
			else {
				
				Tekuna :: getLogger(__CLASS__) -> info("Detected encoding '$sEncoding' for rendered decorator '$sDecoratorFile'.");
				$sAfterDecoration = mb_convert_encoding($sAfterDecoration, mb_internal_encoding(), $sEncoding);
			}
						
			return $sAfterDecoration;
		}
	}
